001 /*
002 * Copyright 2005 Stephen J. McConnell.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
013 * implied.
014 *
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019 package net.dpml.tools.impl;
020
021 import java.io.File;
022 import java.io.IOException;
023 import java.net.URL;
024 import java.net.URI;
025 import java.util.Date;
026
027 import net.dpml.lang.Plugin;
028 import net.dpml.lang.Part;
029
030 import net.dpml.library.info.Scope;
031 import net.dpml.library.Library;
032 import net.dpml.library.Resource;
033 import net.dpml.library.Type;
034 import net.dpml.library.Filter;
035 import net.dpml.library.impl.DefaultLibrary;
036
037 import net.dpml.tools.info.ListenerDirective;
038
039 import net.dpml.tools.Context;
040
041 import net.dpml.transit.Artifact;
042 import net.dpml.transit.Transit;
043 import net.dpml.transit.link.LinkManager;
044 import net.dpml.transit.Layout;
045 import net.dpml.transit.ClassicLayout;
046
047 import net.dpml.util.Logger;
048 import net.dpml.util.DefaultLogger;
049
050 import org.apache.tools.ant.Project;
051 import org.apache.tools.ant.BuildException;
052 import org.apache.tools.ant.BuildListener;
053 import org.apache.tools.ant.BuildEvent;
054 import org.apache.tools.ant.types.Path;
055
056 /**
057 * Default implementation of a project context.
058 *
059 * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
060 * @version 1.1.1
061 */
062 public final class DefaultContext implements Context
063 {
064 private static final Layout CLASSIC_LAYOUT = new ClassicLayout();
065
066 private final Project m_project;
067 private final Resource m_resource;
068
069 private Path m_runtime;
070 private Path m_test;
071
072 /**
073 * Creation of a new project build context.
074 * @param project the unconfigured Ant project
075 * @exception Exception if an error occurs during context extablishment
076 */
077 public DefaultContext( Project project ) throws Exception
078 {
079 this(
080 newResource( project.getBaseDir() ),
081 project );
082 }
083
084 /**
085 * Creation of a new project build context.
086 * @param resource the resource definition
087 * @param project the Ant project
088 * @exception Exception if an error occurs during context extablishment
089 */
090 public DefaultContext( Resource resource, Project project ) throws Exception
091 {
092 m_project = project;
093 m_resource = resource;
094
095 Library library = resource.getLibrary();
096 project.addReference( "project.timestamp", new Date() );
097
098 File basedir = resource.getBaseDir();
099 if( !basedir.equals( project.getBaseDir() ) )
100 {
101 project.setBaseDir( resource.getBaseDir() );
102 }
103
104 project.addReference( "project.context", this );
105
106 String[] names = resource.getPropertyNames();
107 for( int i=0; i<names.length; i++ )
108 {
109 String name = names[i];
110 String value = resource.getProperty( name );
111 setProperty( name, value );
112 }
113
114 setProperty( "project.name", resource.getName() );
115 setProperty( "project.version", resource.getVersion() );
116 setProperty( "project.resource.path", resource.getResourcePath() );
117 setProperty( "project.basedir", resource.getBaseDir().toString() );
118
119 File cache = Transit.getInstance().getCacheDirectory();
120 String cachePath = cache.getCanonicalPath();
121 setProperty( "project.cache", cachePath );
122
123 Filter[] filters = resource.getFilters();
124 for( int i=0; i<filters.length; i++ )
125 {
126 Filter filter = filters[i];
127 String token = filter.getToken();
128 try
129 {
130 String value = filter.getValue( resource );
131 String resolved = resource.resolve( value );
132 project.getGlobalFilterSet().addFilter( token, resolved );
133 }
134 catch( Exception e )
135 {
136 final String error =
137 "Error while attempting to setup the filter [" + token + "].";
138 throw new BuildException( error, e );
139 }
140 }
141
142 setProperty( "project.nl", "\n" );
143 setProperty(
144 "project.line",
145 "---------------------------------------------------------------------------\n", false );
146 setProperty(
147 "project.info",
148 "---------------------------------------------------------------------------\n"
149 + resource.getResourcePath()
150 + "#"
151 + resource.getVersion()
152 + "\n---------------------------------------------------------------------------", false );
153
154 setProperty( "project.src.dir", getSrcDirectory().toString() );
155 setProperty( "project.src.main.dir", getSrcMainDirectory().toString() );
156 setProperty( "project.src.test.dir", getSrcTestDirectory().toString() );
157 setProperty( "project.etc.dir", getEtcDirectory().toString() );
158 setProperty( "project.etc.main.dir", getEtcMainDirectory().toString() );
159 setProperty( "project.etc.test.dir", getEtcTestDirectory().toString() );
160 setProperty( "project.etc.data.dir", getEtcDataDirectory().toString() );
161
162 setProperty( "project.target.dir", getTargetDirectory().toString() );
163 setProperty( "project.target.build.main.dir", getTargetBuildMainDirectory().toString() );
164 setProperty( "project.target.build.test.dir", getTargetBuildTestDirectory().toString() );
165 setProperty( "project.target.classes.main.dir", getTargetClassesMainDirectory().toString() );
166 setProperty( "project.target.classes.test.dir", getTargetClassesTestDirectory().toString() );
167 setProperty( "project.target.deliverables.dir", getTargetDeliverablesDirectory().toString() );
168 setProperty( "project.target.test.dir", getTargetTestDirectory().toString() );
169 setProperty( "project.target.reports.dir", getTargetReportsDirectory().toString() );
170
171 // add properties dealing with produced types
172
173 addProductionProperties();
174
175 // add listeners declared in the builder configuration
176
177 ListenerDirective[] listeners = StandardBuilder.CONFIGURATION.getListenerDirectives();
178 for( int i=0; i<listeners.length; i++ )
179 {
180 ListenerDirective directive = listeners[i];
181 project.log( "adding listener: " + directive.getName(), Project.MSG_VERBOSE );
182 BuildListener buildListener = loadBuildListener( directive );
183 project.addBuildListener( buildListener );
184 BuildEvent event = new BuildEvent( project );
185 buildListener.buildStarted( event );
186 }
187 }
188
189 private void addProductionProperties() throws IOException
190 {
191 Resource resource = getResource();
192 Type[] types = resource.getTypes();
193 for( int i=0; i<types.length; i++ )
194 {
195 Type type = types[i];
196 String id = type.getID();
197 File file = getTargetDeliverable( id );
198 String path = file.getCanonicalPath();
199 setProperty( "project.deliverable." + id + ".path", path );
200 File dir = file.getParentFile();
201 String spec = dir.getCanonicalPath();
202 setProperty( "project.deliverable." + id + ".dir", spec );
203 String base = getLayoutBase( id );
204 setProperty( "project.cache." + id + ".dir", base );
205 String address = getLayoutPath( id );
206 setProperty( "project.cache." + id + ".path", address );
207 }
208 }
209
210 private void setProperty( String key, String value )
211 {
212 setProperty( key, value, true );
213 }
214
215 private void setProperty( String key, String value, boolean verbose )
216 {
217 Project project = getProject();
218 String v = project.getProperty( key );
219 if( null == v )
220 {
221 if( verbose )
222 {
223 project.log( "setting property [" + key + "] to [" + value + "]", Project.MSG_VERBOSE );
224 }
225 project.setProperty( key, value );
226 }
227 else if( !value.equals( v ) )
228 {
229 if( verbose )
230 {
231 project.log( "updating property [" + key + "] to [" + value + "]", Project.MSG_VERBOSE );
232 }
233 project.setProperty( key, value );
234 }
235 }
236
237 /**
238 * Initialize the context during which runtime and test path objects are
239 * established as project references.
240 */
241 public void init()
242 {
243 if( m_runtime != null )
244 {
245 return;
246 }
247
248 final Path compileSrcPath = new Path( m_project );
249 File srcMain = getTargetBuildMainDirectory();
250 compileSrcPath.createPathElement().setLocation( srcMain );
251 m_project.addReference( "project.build.src.path", compileSrcPath );
252 m_runtime = createPath( Scope.RUNTIME );
253 m_project.addReference( "project.compile.path", m_runtime );
254 m_test = createPath( Scope.TEST );
255 if( m_resource.isa( "jar" ) )
256 {
257 File deliverables = getTargetDeliverablesDirectory();
258 File jars = new File( deliverables, "jars" );
259 String filename = getLayoutFilename( "jar" );
260 File jar = new File( jars, filename );
261 m_test.createPathElement().setLocation( jar );
262 }
263 final File testClasses = getTargetClassesTestDirectory();
264 m_test.createPathElement().setLocation( testClasses );
265 m_project.addReference( "project.test.path", m_test );
266 }
267
268 /**
269 * Return the associated project.
270 * @return the ant project
271 */
272 public Project getProject()
273 {
274 return m_project;
275 }
276
277 /**
278 * Return the value of a property.
279 * @param key the property key
280 * @return the property value or null if undefined
281 */
282 public String getProperty( String key )
283 {
284 return getProperty( key, null );
285 }
286
287 /**
288 * Return the value of a property. If the project contains a declaration
289 * for the property then that value will be returned, otherwise the property
290 * will be resolved relative to the current resource.
291 *
292 * @param key the property key
293 * @param value the default value
294 * @return the property value or null if undefined
295 */
296 public String getProperty( String key, String value )
297 {
298 String result = m_project.getProperty( key );
299 if( null != result )
300 {
301 return result;
302 }
303 else
304 {
305 return getResource().getProperty( key, value );
306 }
307 }
308
309 /**
310 * Return an Ant path suitable for comile or runtime usage. If the supplied scope is
311 * less than Scope.RUNTIME a runtime path is returned otherwise the test path is
312 * returned.
313 * @param scope the build scope
314 * @return the path object
315 */
316 public Path getPath( Scope scope )
317 {
318 if( m_runtime == null )
319 {
320 init();
321 }
322 if( scope.isLessThan( Scope.TEST ) )
323 {
324 return m_runtime;
325 }
326 else
327 {
328 return m_test;
329 }
330 }
331
332 /**
333 * Return the active resource.
334 * @return the resource definition
335 */
336 public Resource getResource()
337 {
338 return m_resource;
339 }
340
341 /**
342 * Return the resource library.
343 * @return the library
344 */
345 public Library getLibrary()
346 {
347 return m_resource.getLibrary();
348 }
349
350 /**
351 * Return the project source directory.
352 * @return the directory
353 */
354 public File getSrcDirectory()
355 {
356 return createFile( "src" );
357 }
358
359 /**
360 * Return the project source main directory.
361 * @return the directory
362 */
363 public File getSrcMainDirectory()
364 {
365 String path = getProperty( "project.src.main", "src/main" );
366 return createFile( path );
367 }
368
369 /**
370 * Return the project source test directory.
371 * @return the directory
372 */
373 public File getSrcTestDirectory()
374 {
375 String path = getProperty( "project.src.test", "src/test" );
376 return createFile( path );
377 }
378
379 /**
380 * Return the project source docs directory.
381 * @return the directory
382 */
383 public File getSrcDocsDirectory()
384 {
385 String path = getProperty( "project.src.docs", "src/docs" );
386 return createFile( path );
387 }
388
389 /**
390 * Return the project etc directory.
391 * @return the directory
392 */
393 public File getEtcDirectory()
394 {
395 String path = getProperty( "project.etc", "etc" );
396 return createFile( path );
397 }
398
399 /**
400 * Return the project etc/main directory.
401 * @return the directory
402 */
403 public File getEtcMainDirectory()
404 {
405 String path = getProperty( "project.etc.main", "etc/main" );
406 return createFile( path );
407 }
408
409 /**
410 * Return the project etc/test directory.
411 * @return the directory
412 */
413 public File getEtcTestDirectory()
414 {
415 String path = getProperty( "project.etc.test", "etc/test" );
416 return createFile( path );
417 }
418
419 /**
420 * Return the project etc/resources directory.
421 * @return the directory
422 */
423 public File getEtcDataDirectory()
424 {
425 String path = getProperty( "project.etc.data", "etc/data" );
426 return createFile( path );
427 }
428
429 /**
430 * Return the project target directory.
431 * @return the directory
432 */
433 public File getTargetDirectory()
434 {
435 return createFile( "target" );
436 }
437
438 /**
439 * Return a directory within the target directory.
440 * @param path the path
441 * @return the directory
442 */
443 public File getTargetDirectory( String path )
444 {
445 return new File( getTargetDirectory(), path );
446 }
447
448 /**
449 * Return the project target temp directory.
450 * @return the directory
451 */
452 public File getTargetTempDirectory()
453 {
454 return new File( getTargetDirectory(), "temp" );
455 }
456
457 /**
458 * Return the project target build directory.
459 * @return the directory
460 */
461 public File getTargetBuildDirectory()
462 {
463 return new File( getTargetDirectory(), "build" );
464 }
465
466 /**
467 * Return the project target build main directory.
468 * @return the directory
469 */
470 public File getTargetBuildMainDirectory()
471 {
472 return new File( getTargetBuildDirectory(), "main" );
473 }
474
475 /**
476 * Return the project target build test directory.
477 * @return the directory
478 */
479 public File getTargetBuildTestDirectory()
480 {
481 return new File( getTargetBuildDirectory(), "test" );
482 }
483
484 /**
485 * Return the project target build docs directory.
486 * @return the directory
487 */
488 public File getTargetBuildDocsDirectory()
489 {
490 return new File( getTargetBuildDirectory(), "docs" );
491 }
492
493 /**
494 * Return the project target root classes directory.
495 * @return the directory
496 */
497 public File getTargetClassesDirectory()
498 {
499 return new File( getTargetDirectory(), "classes" );
500 }
501
502 /**
503 * Return the project target main classes directory.
504 * @return the directory
505 */
506 public File getTargetClassesMainDirectory()
507 {
508 return new File( getTargetClassesDirectory(), "main" );
509 }
510
511 /**
512 * Return the project target test classes directory.
513 * @return the directory
514 */
515 public File getTargetClassesTestDirectory()
516 {
517 return new File( getTargetClassesDirectory(), "test" );
518 }
519
520 /**
521 * Return the project target reports directory.
522 * @return the directory
523 */
524 public File getTargetReportsDirectory()
525 {
526 return new File( getTargetDirectory(), "reports" );
527 }
528
529 /**
530 * Return the project target test reports directory.
531 * @return the directory
532 */
533 public File getTargetReportsTestDirectory()
534 {
535 return new File( getTargetReportsDirectory(), "test" );
536 }
537
538 /**
539 * Return the project target main reports directory.
540 * @return the directory
541 */
542 public File getTargetReportsMainDirectory()
543 {
544 return new File( getTargetReportsDirectory(), "main" );
545 }
546
547 /**
548 * Return the project target javadoc reports directory.
549 * @return the directory
550 */
551 public File getTargetReportsJavadocDirectory()
552 {
553 return new File( getTargetReportsDirectory(), "api" );
554 }
555
556 /**
557 * Return the project target reports docs directory.
558 * @return the directory
559 */
560 public File getTargetDocsDirectory()
561 {
562 return new File( getTargetDirectory(), "docs" );
563 }
564
565 /**
566 * Return the project target test directory.
567 * @return the directory
568 */
569 public File getTargetTestDirectory()
570 {
571 return new File( getTargetDirectory(), "test" );
572 }
573
574 /**
575 * Return the project target deliverables directory.
576 * @return the directory
577 */
578 public File getTargetDeliverablesDirectory()
579 {
580 return new File( getTargetDirectory(), "deliverables" );
581 }
582
583 /**
584 * Return the project target deliverables directory.
585 * @param type the deliverable type
586 * @return the directory
587 */
588 public File getTargetDeliverable( String type )
589 {
590 Artifact artifact = m_resource.getArtifact( type );
591 String path = CLASSIC_LAYOUT.resolveFilename( artifact );
592 String types = type + "s";
593 File root = new File( getTargetDeliverablesDirectory(), types );
594 return new File( root, path );
595 }
596
597 /**
598 * Create a file relative to the resource basedir.
599 * @param path the relative path
600 * @return the directory
601 */
602 public File createFile( String path )
603 {
604 File basedir = m_resource.getBaseDir();
605 return new File( basedir, path );
606 }
607
608 /**
609 * Return a filename using the layout strategy employed by the cache.
610 * @param id the artifact type
611 * @return the filename
612 */
613 public String getLayoutFilename( String id )
614 {
615 Artifact artifact = m_resource.getArtifact( id );
616 return Transit.getInstance().getCacheLayout().resolveFilename( artifact );
617 }
618
619 /**
620 * Return the directory path representing the module structure and type
621 * using the layout strategy employed by the cache.
622 * @param id the artifact type
623 * @return the path from the root of the cache to the directory containing the artifact
624 */
625 public String getLayoutBase( String id )
626 {
627 Artifact artifact = m_resource.getArtifact( id );
628 return Transit.getInstance().getCacheLayout().resolveBase( artifact );
629 }
630
631 /**
632 * Return the full path to an artifact using the layout employed by the cache.
633 * @param id the artifact type
634 * @return the full path including base path and filename
635 */
636 public String getLayoutPath( String id )
637 {
638 Artifact artifact = m_resource.getArtifact( id );
639 return Transit.getInstance().getCacheLayout().resolvePath( artifact );
640 }
641
642 /**
643 * Utility operation to construct a new classpath path instance.
644 * @param scope the build scope
645 * @return the path
646 */
647 public Path createPath( Scope scope )
648 {
649 try
650 {
651 Resource[] resources = m_resource.getClasspathProviders( scope );
652 return createPath( resources, true, true );
653 }
654 catch( Exception e )
655 {
656 final String error =
657 "Unexpected error while constructing path instance for the scope: " + scope;
658 throw new RuntimeException( error, e );
659 }
660 }
661
662 /**
663 * Utility operation to construct a new path using a supplied array of resources.
664 * @param resources the resource to use in path construction
665 * @return the path
666 */
667 public Path createPath( Resource[] resources )
668 {
669 return createPath( resources, true, false );
670 }
671
672 /**
673 * Utility operation to construct a new path using a supplied array of resources.
674 * @param resources the resources to use in path construction
675 * @param resolve if true force local caching of the artifact
676 * @param filter if true restrict path entries to resources that produce jars
677 * @return the path
678 */
679 public Path createPath( Resource[] resources, boolean resolve, boolean filter )
680 {
681 final Path path = new Path( m_project );
682 File cache = (File) m_project.getReference( "dpml.cache" );
683 for( int i=0; i<resources.length; i++ )
684 {
685 Resource resource = resources[i];
686 if( !resource.equals( getResource() ) )
687 {
688 if( filter && resource.isa( "jar" ) )
689 {
690 Artifact artifact = resource.getArtifact( "jar" );
691 addToPath( cache, path, artifact, resolve );
692 }
693 else
694 {
695 Type[] types = resource.getTypes();
696 for( int j=0; j<types.length; j++ )
697 {
698 Artifact artifact = resource.getArtifact( types[j].getID() );
699 addToPath( cache, path, artifact, resolve );
700 }
701 }
702 }
703 }
704 return path;
705 }
706
707 private void addToPath( File cache, Path path, Artifact artifact, boolean resolve )
708 {
709 Artifact target = getTargetArtifact( artifact );
710 String location = Transit.getInstance().getCacheLayout().resolvePath( target );
711 File file = new File( cache, location );
712 path.createPathElement().setLocation( file );
713
714 if( resolve )
715 {
716 resolveArtifact( artifact );
717 }
718 }
719
720 private Artifact getTargetArtifact( Artifact artifact )
721 {
722 String scheme = artifact.getScheme();
723 if( !Artifact.LINK.equals( scheme ) )
724 {
725 return artifact;
726 }
727 else
728 {
729 try
730 {
731 LinkManager manager = Transit.getInstance().getLinkManager();
732 URI uri = manager.getTargetURI( artifact.toURI() );
733 return Artifact.createArtifact( uri );
734 }
735 catch( IOException e )
736 {
737 final String error =
738 "Unable to resolve link artifact ["
739 + artifact
740 + "].";
741 throw new BuildException( error, e );
742 }
743 }
744 }
745
746 private void resolveArtifact( Artifact artifact )
747 {
748 try
749 {
750 URL url = artifact.toURL();
751 url.openStream();
752 }
753 catch( IOException e )
754 {
755 final String error =
756 "Unable to resolve artifact ["
757 + artifact
758 + "].";
759 throw new BuildException( error, e );
760 }
761 }
762
763 private static Resource newResource( File basedir ) throws Exception
764 {
765 Logger logger = new DefaultLogger();
766 DefaultLibrary library = new DefaultLibrary( logger );
767 return library.locate( basedir.getCanonicalFile() );
768 }
769
770 private static String flatternDependencies( String[] deps )
771 {
772 if( deps.length == 0 )
773 {
774 return null;
775 }
776 StringBuffer buffer = new StringBuffer();
777 for( int i=0; i<deps.length; i++ )
778 {
779 if( i>0 )
780 {
781 buffer.append( "," );
782 }
783 String dep = deps[i];
784 buffer.append( dep );
785 }
786 return buffer.toString();
787 }
788
789 private BuildListener loadBuildListener( ListenerDirective listener )
790 {
791 String name = listener.getName();
792 URI uri = listener.getURI();
793 try
794 {
795 String classname = listener.getClassname();
796 Object object = loadInstance( name, uri, classname );
797 return (BuildListener) object;
798 }
799 catch( ClassCastException e )
800 {
801 final String error =
802 "Build listener ["
803 + name
804 + "] from uri ["
805 + uri
806 + "] does not implement "
807 + BuildListener.class.getName();
808 throw new BuilderError( error );
809 }
810 }
811
812 private Object loadInstance( String name, URI uri, String classname )
813 {
814 if( null == uri )
815 {
816 try
817 {
818 ClassLoader classloader = getClass().getClassLoader();
819 Class clazz = classloader.loadClass( classname );
820 Object[] args = new Object[]{this};
821 return Plugin.instantiate( clazz, args );
822 }
823 catch( Throwable e )
824 {
825 final String error =
826 "Internal error while attempting to load a local plugin."
827 + "\nClass: " + classname
828 + "\nName: " + name;
829 throw new BuilderError( error, e );
830 }
831 }
832 else
833 {
834 ClassLoader context = Thread.currentThread().getContextClassLoader();
835 try
836 {
837 ClassLoader classloader = getClass().getClassLoader();
838 Thread.currentThread().setContextClassLoader( classloader );
839 Object[] params = new Object[]{this};
840 Part part = Part.load( uri );
841 if( null == classname )
842 {
843 return part.instantiate( params );
844 }
845 else
846 {
847 ClassLoader loader = part.getClassLoader();
848 Class c = loader.loadClass( classname );
849 return Plugin.instantiate( c, params );
850 }
851 }
852 catch( Throwable e )
853 {
854 final String error =
855 "Internal error while attempting to load plugin."
856 + "\nURI: " + uri
857 + "\nName: " + name;
858 throw new BuilderError( error, e );
859 }
860 finally
861 {
862 Thread.currentThread().setContextClassLoader( context );
863 }
864 }
865 }
866 }
867